/*
 * Copyright (C) 2016  Nicola Spanti (RyDroid) <dev@nicola-spanti.info>
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */


#include <check.h>
#include "PlanetWars2dRT-core/specific/model/ship.h"
#include "PlanetWars2dRT-core/specific/controller/ship.h"
#include "check_observable_generic.h"


struct ship_t     a_ship;
struct observer_t an_observer;

void
tests_setup_init(void)
{
  struct position_2d_uint position;
  position.x = 0;
  position.y = 0;
  a_ship = ship_create_default(&position, &position);
  
  observer_init_first_time_only_unsafe(&an_observer);
}

void
tests_setup_free(void)
{
  ship_destructor(&a_ship);
  observer_destructor_unsafe(&an_observer);
}


bool
ship_add_observer_generic(void* a_ship,
			  const observer_t* an_observer)
{
  return ship_add_observer(a_ship, an_observer);
}

void
ship_remove_observers_generic(void* a_ship)
{
  ship_remove_observers(a_ship);
}

void
ship_notify_observers_generic(void* a_ship, void* data)
{
  ship_notify_observers(a_ship, data);
}

size_t
ship_get_nb_observers_generic(void* a_ship)
{
  return ship_get_nb_observers(a_ship);
}

START_TEST(test_ship_add_observer)
{
  test_add_observer_generic(&a_ship,
			    ship_add_observer_generic);
}
END_TEST

START_TEST(test_ship_remove_observers)
{
  test_remove_observers_generic(&a_ship,
				ship_add_observer_generic,
				ship_remove_observers_generic,
				ship_get_nb_observers_generic);
}
END_TEST

START_TEST(test_ship_notify_observers)
{
  test_notify_observers_generic(&a_ship,
				ship_add_observer_generic,
				ship_notify_observers_generic);
}
END_TEST


void
ship_observer_on_move_counter_handler(void* an_observable,
				      void* observer_data,
				      void* event_data)
{
  if(an_observable != NULL && event_data != NULL)
    {
      const observer_data_with_code* data
	= (observer_data_with_code*) event_data;
      if(data->code == SHIP_OBSERVER_CODE_MOVE)
	{
	  ++(*((unsigned int*) observer_data));
	}
    }
}

void
ship_observer_on_life_change_counter_handler(void* an_observable,
					     void* observer_data,
					     void* event_data)
{
  if(an_observable != NULL && event_data != NULL)
    {
      const observer_data_with_code* data
	= (observer_data_with_code*) event_data;
      if(data->code == SHIP_OBSERVER_CODE_LIFE_CHANGE)
	{
	  ++(*((unsigned int*) observer_data));
	}
    }
}

void
ship_observer_on_destroy_counter_handler(void* an_observable,
					     void* observer_data,
					     void* event_data)
{
  if(an_observable != NULL && event_data != NULL)
    {
      const observer_data_with_code* data
	= (observer_data_with_code*) event_data;
      if(data->code == SHIP_OBSERVER_CODE_DESTROY)
	{
	  ++(*((unsigned int*) observer_data));
	}
    }
}

START_TEST(test_ship_event_on_move)
{
  unsigned int counter = 0;
  
  an_observer.handler = ship_observer_on_move_counter_handler;
  an_observer.data = &counter;
  an_observer.data_free = NULL;
  ship_add_observer(&a_ship, &an_observer);
  
  ck_assert(ship_set_width(&a_ship, 1));
  ck_assert_uint_eq(counter, 0);
  ck_assert(ship_set_width(&a_ship, 2));
  ck_assert_uint_eq(counter, 0);
  
  ck_assert(ship_set_height(&a_ship, 1));
  ck_assert_uint_eq(counter, 0);
  ck_assert(ship_set_height(&a_ship, 2));
  ck_assert_uint_eq(counter, 0);
  
  ck_assert(ship_set_power(&a_ship, 1));
  ck_assert_uint_eq(counter, 0);
  ck_assert(ship_set_power(&a_ship, 2));
  ck_assert_uint_eq(counter, 0);
  
  ship_set_top_left_position_current_x(&a_ship, 0);
  ship_set_top_left_position_to_go_x  (&a_ship, 0);
  ck_assert(!ship_move_once_no_notify(&a_ship));
  ck_assert_uint_eq(counter, 0);
  ck_assert(!ship_move_once(&a_ship));
  ck_assert_uint_eq(counter, 0);
  ck_assert(!ship_move_once(&a_ship));
  ck_assert_uint_eq(counter, 0);
  
  ship_set_top_left_position_current_y(&a_ship, 0);
  ship_set_top_left_position_to_go_y  (&a_ship, 0);
  ck_assert(!ship_move_once_no_notify(&a_ship));
  ck_assert_uint_eq(counter, 0);
  ck_assert(!ship_move_once(&a_ship));
  ck_assert_uint_eq(counter, 0);
  ck_assert(!ship_move_once(&a_ship));
  ck_assert_uint_eq(counter, 0);
  
  ship_set_top_left_position_current_x(&a_ship, 0);
  ship_set_top_left_position_to_go_x  (&a_ship, 1);
  ck_assert(ship_move_once_no_notify(&a_ship));
  ck_assert_uint_eq(counter, 0);
  ship_move_once_no_notify_returns_void(&a_ship);
  ck_assert_uint_eq(counter, 0);
  
  ship_set_top_left_position_current_y(&a_ship, 0);
  ship_set_top_left_position_to_go_y  (&a_ship, 1);
  ck_assert(ship_move_once_no_notify(&a_ship));
  ck_assert_uint_eq(counter, 0);
  ship_move_once_no_notify_returns_void(&a_ship);
  ck_assert_uint_eq(counter, 0);
  
  ship_set_top_left_position_current_y(&a_ship, 0);
  ship_set_top_left_position_to_go_y  (&a_ship, 1);
  ck_assert(ship_move_no_notify(&a_ship, 5));
  ck_assert_uint_eq(counter, 0);
  ship_move_no_notify_returns_void(&a_ship, 5);
  ck_assert_uint_eq(counter, 0);
  
  ship_set_top_left_position_current_x(&a_ship, 0);
  ship_set_top_left_position_to_go_x  (&a_ship, 1);
  ck_assert(ship_move_once(&a_ship));
  ck_assert_uint_eq(counter, 1);
  ck_assert(!ship_move_once(&a_ship));
  ck_assert_uint_eq(counter, 1);
  
  ship_set_top_left_position_current_y(&a_ship, 0);
  ship_set_top_left_position_to_go_y  (&a_ship, 1);
  ck_assert(ship_move_once(&a_ship));
  ck_assert_uint_eq(counter, 2);
  ck_assert(!ship_move_once(&a_ship));
  ck_assert_uint_eq(counter, 2);
  
  ship_set_top_left_position_current_x(&a_ship, 0);
  ship_set_top_left_position_to_go_x  (&a_ship, 1);
  ck_assert(ship_move(&a_ship, 5));
  ck_assert_uint_eq(counter, 3);
  ck_assert(!ship_move(&a_ship, 5));
  ck_assert_uint_eq(counter, 3);
  
  ship_set_top_left_position_current_y(&a_ship, 0);
  ship_set_top_left_position_to_go_y  (&a_ship, 1);
  ck_assert(ship_move(&a_ship, 5));
  ck_assert_uint_eq(counter, 4);
  ck_assert(!ship_move(&a_ship, 5));
  ck_assert_uint_eq(counter, 4);
  
  ship_set_top_left_position_current_x(&a_ship, 1);
  ship_set_top_left_position_to_go_x  (&a_ship, 0);
  ck_assert(ship_move(&a_ship, 5));
  ck_assert_uint_eq(counter, 5);
  ck_assert(!ship_move(&a_ship, 5));
  ck_assert_uint_eq(counter, 5);
  
  ship_set_top_left_position_current_y(&a_ship, 1);
  ship_set_top_left_position_to_go_y  (&a_ship, 0);
  ck_assert(ship_move(&a_ship, 5));
  ck_assert_uint_eq(counter, 6);
  ck_assert(!ship_move(&a_ship, 5));
  ck_assert_uint_eq(counter, 6);
}
END_TEST

START_TEST(test_ship_event_on_life_change)
{
  unsigned int counter = 0;
  
  an_observer.handler = ship_observer_on_life_change_counter_handler;
  an_observer.data = &counter;
  an_observer.data_free = NULL;
  ship_add_observer(&a_ship, &an_observer);
  
  ship_reduce_life(&a_ship, 0);
  ck_assert_uint_eq(counter, 0);
  ship_reduce_life(&a_ship, 0);
  ck_assert_uint_eq(counter, 0);
  
  ship_reduce_life(&a_ship, 1);
  ck_assert_uint_eq(counter, 1);
}
END_TEST

START_TEST(test_ship_event_on_destroy)
{
  unsigned int counter = 0;
  
  an_observer.handler = ship_observer_on_destroy_counter_handler;
  an_observer.data = &counter;
  an_observer.data_free = NULL;
  ship_add_observer(&a_ship, &an_observer);
  ck_assert_uint_eq(counter, 0);
  
  ship_set_top_left_position_current_x(&a_ship, 0);
  ship_set_top_left_position_to_go_x  (&a_ship, 1);
  ck_assert(ship_move_once(&a_ship));
  ck_assert_uint_eq(counter, 0);
  
  ship_set_top_left_position_current_y(&a_ship, 0);
  ship_set_top_left_position_to_go_y  (&a_ship, 1);
  ck_assert(ship_move_once(&a_ship));
  ck_assert_uint_eq(counter, 0);
  
  ship_set_top_left_position_current_x(&a_ship, 1);
  ship_set_top_left_position_to_go_x  (&a_ship, 0);
  ck_assert(ship_move_once(&a_ship));
  ck_assert_uint_eq(counter, 0);
  
  ship_set_top_left_position_current_y(&a_ship, 1);
  ship_set_top_left_position_to_go_y  (&a_ship, 0);
  ck_assert(ship_move_once(&a_ship));
  ck_assert_uint_eq(counter, 0);
  
  ck_assert(ship_set_width(&a_ship, 1));
  ck_assert_uint_eq(counter, 0);
  ck_assert(ship_set_width(&a_ship, 2));
  ck_assert_uint_eq(counter, 0);
  
  ck_assert(ship_set_height(&a_ship, 1));
  ck_assert_uint_eq(counter, 0);
  ck_assert(ship_set_height(&a_ship, 2));
  ck_assert_uint_eq(counter, 0);
  
  ck_assert(ship_set_power(&a_ship, 1));
  ck_assert_uint_eq(counter, 0);
  ck_assert(ship_set_power(&a_ship, 2));
  ck_assert_uint_eq(counter, 0);
  
  ship_destroy(&a_ship);
  ck_assert(ship_is_destroyed(&a_ship));
  ck_assert_uint_eq(counter, 1);
  
  ship_destroy(&a_ship);
  ck_assert(ship_is_destroyed(&a_ship));
  ck_assert_uint_eq(counter, 1);
}
END_TEST


Suite*
ship_observers_suite(void)
{
  Suite* s = suite_create("Observers of ship");
  
  TCase* tc_ship_observers_base = tcase_create("Base");
  tcase_add_checked_fixture(tc_ship_observers_base,
			    tests_setup_init, tests_setup_free);
  tcase_add_test(tc_ship_observers_base,
		 test_ship_add_observer);
  tcase_add_test(tc_ship_observers_base,
		 test_ship_remove_observers);
  tcase_add_test(tc_ship_observers_base,
		 test_ship_notify_observers);
  suite_add_tcase(s, tc_ship_observers_base);
  
  TCase* tc_ship_observers_events = tcase_create("Events");
  tcase_add_checked_fixture(tc_ship_observers_events,
			    tests_setup_init, tests_setup_free);
  tcase_add_test(tc_ship_observers_events,
		 test_ship_event_on_move);
  tcase_add_test(tc_ship_observers_events,
		 test_ship_event_on_life_change);
  tcase_add_test(tc_ship_observers_events,
		 test_ship_event_on_destroy);
  suite_add_tcase(s, tc_ship_observers_events);
  
  return s;
}

int
main(void)
{
  size_t number_failed;
  Suite* s;
  SRunner* sr;
  
  s = ship_observers_suite();
  sr = srunner_create(s);
  
  srunner_run_all(sr, CK_NORMAL);
  number_failed = srunner_ntests_failed(sr);
  srunner_free(sr);
  
  return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
}
